package internal

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"time"

	"bilinadmin/pkg/rpc"

	"bilinadmin/pkg/db/redisex"

	"bilinadmin/internal/dao"
	"bilinadmin/internal/service"
	"bilinadmin/pkg/mail"

	"github.com/go-playground/validator/v10"
	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	"github.com/labstack/echo/v4"
	echo_middleware "github.com/labstack/echo/v4/middleware"
	"google.golang.org/grpc"

	"bilinadmin/internal/conf"
	mq_handler "bilinadmin/internal/interface/mq"
	"bilinadmin/internal/interface/rest"
	rpc_handler "bilinadmin/internal/interface/rpc"
	"bilinadmin/pkg/db/pgex"
	"bilinadmin/pkg/log"
	"bilinadmin/pkg/middleware"
	"bilinadmin/pkg/mq"
)

type (
	server struct {
		cfg            *conf.Config
		logger         log.Factory
		echo           *echo.Echo
		grpc           *grpc.Server
		pgsql          *pgex.SqlEx
		redis          *redisex.RedisEX
		nsqConsumer    *mq.NsqConsumer
		accClientPool  *rpc.Pool
		openClientPool *rpc.Pool
	}
	// customValidator 自定义 validator
	customValidator struct {
		validator *validator.Validate
	}
)

// NewServer new server
func NewServer(logger log.Factory, cfg *conf.Config) *server {
	return &server{
		cfg:    cfg,
		logger: logger,
	}
}

// Validate validate
func (cv *customValidator) Validate(i interface{}) error {
	return cv.validator.Struct(i)
}

func defaultRouters(e *echo.Echo) {
	e.GET("/", func(c echo.Context) error {
		return c.String(http.StatusOK, "少侠，你骨骼惊奇！")
	})
	e.GET("/ping", func(c echo.Context) error {
		return c.String(http.StatusOK, "pong")
	})
	e.HEAD("/ping", func(c echo.Context) error {
		return c.String(http.StatusOK, "pong")
	})
}

// rest server
func (s *server) initRestServer() {
	e := echo.New()
	s.echo = e
	e.Debug = s.cfg.Base.Name != "production"
	e.Validator = &customValidator{validator: validator.New()}
	e.Use(echo_middleware.CORSWithConfig(echo_middleware.CORSConfig{
		AllowOrigins: []string{"*"},
		AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
		AllowHeaders: []string{"Content-Type", "Authorization", "G-Token", "B-Token"},
	}))
	e.Use(middleware.EchoTracer, middleware.EchoRequestID, middleware.EchoStandardLogger, middleware.EchoErrorHandler, middleware.EchoRecover)
	e.HTTPErrorHandler = middleware.UnCatchErrorHandler
	// default router
	defaultRouters(e)
	// base handler
	rest.InitRestBase(s.logger, s.cfg)
	// brand handler
	bRouter := e.Group("/admin/api/brand")
	bHandler := rest.NewRestBrandHandler()
	bHandler.Register(bRouter)
	// employee handler
	eRouter := e.Group("/admin/api/employee")
	eHandler := rest.NewRestEmployeeHandler()
	eHandler.Register(eRouter)
	// role permission
	rRouter := e.Group("/admin/api/role")
	rHandler := rest.NewPermissionHandler()
	rHandler.Register(rRouter)
	// vendor
	vRouter := e.Group("/admin/api/vendor")
	vHandler := rest.NewRestVendorHandler()
	vHandler.Register(vRouter)
}

// grpc server
func (s *server) initGrpcServer() {
	s.grpc = grpc.NewServer(
		grpc.UnaryInterceptor(
			grpc_middleware.ChainUnaryServer(
				middleware.GrpcUnaryTracerInterceptor(),
				middleware.GrpcUnaryRequestIDInterceptor(),
				middleware.GrpcUnaryStdLoggerInterceptor(),
				middleware.GrpcUnaryRecoveryInterceptor(),
				middleware.GrpcUnaryValidatorInterceptor(),
				middleware.GrpcUnaryErrorInterceptor(),
			)))
	// rpc base
	rpc_handler.InitRpcBase(s.logger, s.cfg)
	// brand handler
	bHandler := rpc_handler.NewBrandRpcHandler()
	bHandler.Register(s.grpc)
	// employee handler
	eHandler := rpc_handler.NewEmployeeHandler()
	eHandler.Register(s.grpc)
	// permission handler
	pHandler := rpc_handler.NewPermissionRpcHandler()
	pHandler.Register(s.grpc)
	// vendor handler
	vHandler := rpc_handler.NewVendorRpcHandler()
	vHandler.Register(s.grpc)
}

// nsq consumer
func (s *server) initNsqConsumer() {
	s.nsqConsumer = mq.NewNsqConsumer([]string{s.cfg.NSQConf.LookupAddress}, 10*time.Second, 2)
	s.nsqConsumer.Use(middleware.MqTracer, middleware.MqRequestId, middleware.MqRecovery)
	// mq handler
	mq_handler.InitMqBase(s.logger, s.cfg)
	mHandler := mq_handler.NewMqHandler()
	mHandler.Register(s.nsqConsumer)
}

// Close gracefully close server
func (s *server) Close() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	if s.echo != nil {
		_ = s.echo.Shutdown(ctx)
	}
	if s.grpc != nil {
		s.grpc.GracefulStop()
	}
	if s.pgsql != nil {
		_ = s.pgsql.Close()
	}
	if s.redis != nil {
		_ = s.redis.Close()
	}
	if s.nsqConsumer != nil {
		s.nsqConsumer.Close()
	}
	if s.accClientPool != nil {
		s.accClientPool.Close()
	}
	if s.openClientPool != nil {
		s.openClientPool.Close()
	}
}

func (s *server) initBilinRpcPools() {
	// 统一账号
	clientPool, err := rpc.New(func() (*grpc.ClientConn, error) {
		return rpc.NewGrpcConn(s.cfg.RPC.AccountRpc, s.logger)
	}, 3, 10*time.Minute, 30*time.Minute)
	if err != nil {
		s.logger.Bg().Panic(fmt.Sprintf("bilin account rpc server init failed %s", err.Error()))
	}
	s.accClientPool = clientPool
	// GetGSMSClient 获取短信 sms 客户端
	clientPool, err = rpc.New(func() (*grpc.ClientConn, error) {
		return rpc.NewGrpcConn(s.cfg.RPC.OpenRpc, s.logger)
	}, 3, 10*time.Minute, 30*time.Minute)
	if err != nil {
		s.logger.Bg().Panic(fmt.Sprintf("bilin pay rpc server init failed %s", err.Error()))
	}
	s.openClientPool = clientPool
}

// InitDao dao 层初始化
func (s *server) InitDao() {
	// infrastructure
	var err error
	s.pgsql, err = pgex.NewSqlEx(s.cfg.PgSql.Host, s.cfg.PgSql.Port, s.cfg.PgSql.User, s.cfg.PgSql.Password,
		s.cfg.PgSql.Dbname, s.cfg.PgSql.MaxIdleConns, s.cfg.PgSql.MaxOpenConns, s.cfg.PgSql.MaxLifeTime)
	if err != nil {
		s.logger.Bg().Panic(fmt.Sprintf("postgresql error: %s", err.Error()))
	}
	// redis
	cCfg := s.cfg.Redis
	s.redis, err = redisex.NewRedisEX(cCfg.Address, cCfg.Password, cCfg.DefaultDB, cCfg.MaxRetries, cCfg.IdleTimeout)
	if err != nil {
		s.logger.Bg().Panic(fmt.Sprintf("redis init failed: %s", err.Error()))
	}
	// rpc 客户端连接池初始化
	s.initBilinRpcPools()
	nsq := mq.NewNsqProducer(s.cfg.NSQConf.NSQAddress)
	dao.InitDao(s.cfg, s.pgsql, s.redis, nsq)

	// dao layer
	dao.InitBrandDao()
	dao.InitEmployeeDao()
	dao.InitStoreDao()
	dao.InitPermissionDao()
	dao.InitRoleDao()
	dao.InitOperatorDao()
	dao.InitInventoryDao()
	dao.InitMessageDao()
	dao.InitBilinVendorDao(s.accClientPool, s.openClientPool)
}

func (s *server) initService() {
	// service 基类
	service.InitService(s.logger, s.cfg)
	// brand service
	service.InitBrandService()
	// employee service
	service.InitEmployeeService()
	// role permission service
	service.InitRolePermissionService()
	// vendor
	service.InitVendorService(s.cfg)
}

// 初始通知邮件
func (s *server) initBugNotify() {
	// 邮件
	if s.cfg.Base.BugAccount != "" && s.cfg.Base.BugAccountPwd != "" {
		cfg := &mail.Config{
			Addr:     "smtp.exmail.qq.com:465",
			From:     s.cfg.Base.BugAccount,
			Password: s.cfg.Base.BugAccountPwd,
			StartSSL: true,
		}
		_ = mail.Init(cfg)
	}
}

func (s *server) initComponents() {
	// dao layer
	s.InitDao()
	// service layer
	s.initService()
}

func (s *server) start() {
	if s.echo != nil {
		// http server
		go func() {
			s.logger.Bg().Info(fmt.Sprintf("Http Server start at: %s", s.cfg.HTTP.Address))
			s.echo.HideBanner = true
			err := s.echo.Start(s.cfg.HTTP.Address)
			if err != nil {
				s.logger.Bg().Fatal(fmt.Sprintf("Http Server Failed: %s", err.Error()))
			}
		}()
	}
	if s.grpc != nil {
		// grpc server
		go func() {
			// init grpc
			lis, err := net.Listen("tcp", s.cfg.RPC.Port)
			if err != nil {
				s.logger.Bg().Panic(fmt.Sprintf("Grpc Server Failed: %v", err))
			}
			s.logger.Bg().Info(fmt.Sprintf("Grpc Server Start at: %s", s.cfg.RPC.Port))
			if err = s.grpc.Serve(lis); err != nil {
				s.logger.Bg().Panic(fmt.Sprintf("Grpc Server Failed: %v", err))
			}
		}()
	}
	if s.nsqConsumer != nil {
		if err := s.nsqConsumer.Start(); err != nil {
			s.logger.Bg().Panic(fmt.Sprintf("Nsq Consumer Failed: %v", err))
		}
		s.logger.Bg().Info(fmt.Sprintf("nsq consumers started%s", ""))
	}
}

// Run 运行
func (s *server) Run() {
	// 组件初始化
	s.initComponents()
	// middleware prepare
	middleware.InitLogger(s.logger)
	// rest 服务初始化
	s.initRestServer()
	// rpc 服务初始化
	s.initGrpcServer()
	// nsq 服务初始化
	s.initNsqConsumer()
	// bug 邮件初始化
	s.initBugNotify()
	// 启动服务
	s.start()
}
